package com.lhy.common.web.wrapper;

import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.lhy.common.web.entity.ComparatorEnum;
import com.lhy.common.web.entity.ConditionEnum;
import com.lhy.common.web.entity.DbField;
import com.lhy.common.web.entity.SearchObject;
import com.lhy.common.web.util.ReflectionUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.*;
import java.util.stream.Collectors;

@Slf4j
public class WrapperHelper {

    private static final Map<ComparatorEnum, SearchComparator> SEARCH_COMPARATOR_MAP = new HashMap<>();

    static {
        ServiceLoader<SearchComparator> serviceLoader = ServiceLoader.load(SearchComparator.class);
        for (SearchComparator whereCondition : serviceLoader) {
            SEARCH_COMPARATOR_MAP.put(whereCondition.comparatorEnum(), whereCondition);
        }
    }

    private static boolean isCanLikeColumn(Object v) {
        if (v == null) {
            return false;
        }
        return String.class.isAssignableFrom(v.getClass());
    }


    public static <T> QueryWrapper<T> newAndEqQueryWrapper(T bean, Class<T> clazz) {
        Field[] allDeclaredField = ReflectionUtils.getAllDeclaredField(bean);
        List<SearchObject> searchObjects = Arrays.stream(allDeclaredField).map(f -> toEqSearchObject(bean, f)).collect(Collectors.toList());
        return newConditionWrapper(searchObjects, clazz);
    }

    public static <T> QueryWrapper<T> newAndLikeQueryWrapper(T bean, Class<T> clazz) {
        Field[] allDeclaredField = ReflectionUtils.getAllDeclaredField(bean);
        List<SearchObject> searchObjects = Arrays.stream(allDeclaredField).map(f -> toLikeSearchObject(bean, f)).collect(Collectors.toList());
        return newConditionWrapper(searchObjects, clazz);
    }

    private static <T> SearchObject toEqSearchObject(T bean, Field f) {
        return toSearchObject(bean, f, isCanLikeColumn(f) ? ComparatorEnum.LIKE : ComparatorEnum.EQ);
    }

    private static <T> SearchObject toLikeSearchObject(T bean, Field f) {
        return toSearchObject(bean, f, isCanLikeColumn(f) ? ComparatorEnum.LIKE : isCanLikeColumn(ReflectionUtils.getFieldValue(bean, f.getName())) ? ComparatorEnum.LIKE : ComparatorEnum.EQ);
    }

    private static <T> SearchObject toSearchObject(T bean, Field f, ComparatorEnum comparatorEnum) {
        Object fieldValue = ReflectionUtils.getFieldValue(bean, f.getName());
        return new SearchObject().setField(f.getName()).setComparator(comparatorEnum).setCondition(ConditionEnum.AND).setValue(fieldValue == null ? null : fieldValue.toString());
    }

    public static <T> QueryWrapper<T> newConditionWrapper(List<SearchObject> body, Class<T> clazz) {
        if (CollectionUtils.isEmpty(body)) {
            throw new RuntimeException("SearchObject为空");
        }
        // 根据传入的模型类，获取属性名与数据库列名自建的映射关系
        Map<String, String> field2ColumnMap = getDbFieldMap(clazz);
        if (CollectionUtils.isEmpty(field2ColumnMap)) {
            throw new RuntimeException("传入的类" + clazz.getName() + "不是实体对象列");
        }

        QueryWrapper<T> queryWrapper = Wrappers.query();
        for (SearchObject searchObject : body) {
            //设置数据库字段
            String[] filedNames = searchObject.getField().split(",");
            String[] columnNamesBySearchFields = getColumnNamesBySearchField(field2ColumnMap, filedNames).toArray(new String[0]);
            if (columnNamesBySearchFields.length == 0) {
                for (String filedName : filedNames) {
                    log.info("未匹配到的查询字段：{}", filedName);
                }
                continue;
            }
            SearchComparator searchComparator = SEARCH_COMPARATOR_MAP.get(searchObject.getComparator());
            Assert.notNull(searchComparator, "未匹配到searchComparator:" + searchObject.getComparator());
            boolean validSuccess = Arrays.stream(filedNames).anyMatch(t -> searchComparator.valid(new DbField(t, searchObject.getValue())));
            if (validSuccess) {
                switch (searchObject.getCondition()) {
                    case AND:
                        if (columnNamesBySearchFields.length == 1) {
                            accept(columnNamesBySearchFields, searchComparator, queryWrapper, searchObject.getValue());
                        } else {
                            queryWrapper.and(w -> accept(columnNamesBySearchFields, searchComparator, w, searchObject.getValue()));
                        }
                        break;
                    case OR:
                        if (columnNamesBySearchFields.length == 1) {
                            queryWrapper.or();
                            accept(columnNamesBySearchFields, searchComparator, queryWrapper, searchObject.getValue());
                        } else {
                            queryWrapper.or(w -> accept(columnNamesBySearchFields, searchComparator, w, searchObject.getValue()));
                        }
                        break;
                    case BY:
                        accept(columnNamesBySearchFields, searchComparator, queryWrapper, searchObject.getValue());
                        break;
                    default:
                        break;
                }
            }

        }
        return queryWrapper;
    }

    private static <T> void accept(String[] filedNames, SearchComparator searchComparator, QueryWrapper<T> w1, String value) {
        for (int i = 0; i < filedNames.length; i++) {
            String filedName = filedNames[i];
            DbField dbField = new DbField(filedName, value);
            try {
                searchComparator.accept(w1, dbField);
                if (i != filedNames.length - 1) {
                    w1.or();
                }
            } catch (Exception e) {
                log.error("", e);
            }
        }
    }

    /**
     * 将搜索条件中的列转化为数据库列名
     *
     * @param field2ColumnMap
     * @param fields
     * @return
     */
    private static List<String> getColumnNamesBySearchField(Map<String, String> field2ColumnMap, String[] fields) {
        List<String> result = new ArrayList<>();
        if (fields == null || fields.length == 0) {
            return result;
        }
        for (String field : fields) {
            String columnName = field2ColumnMap.get(field);
            if (StringUtils.isBlank(columnName)) {
                log.error(field + "对应的属性不存在数据库列，忽略查询");
                continue;
            }
            result.add(columnName);
        }
        return result;
    }

    /**
     * 将数据库模型对象中的属性名称与数据库的列名的映射关系返回，key为模型属性，value为数据库列名
     *
     * @param clz
     * @return
     */
    private static Map<String, String> getDbFieldMap(Class clz) {
        Map<String, String> result = new HashMap<>();
        Field[] fields = clz.getDeclaredFields();

        if (fields.length == 0) {
            return result;
        }

        for (Field field : fields) {
            TableId tableId = field.getAnnotation(TableId.class);
            if (tableId != null) {
                result.put(field.getName(), tableId.value());
            }
            TableField tableField = field.getAnnotation(TableField.class);
            if (tableField != null) {
                result.put(field.getName(), tableField.value());
            }
            if (tableId == null && tableField == null) {
                result.put(field.getName(), field.getName());
            }
        }
        return result;
    }

    public static <T> void setWrapperWhere(T bean, UpdateWrapper<T> updateWrapper) {
        Class<?> aClass = bean.getClass();
        Field tableIdField = ReflectionUtils.getTableIdField(aClass);
        tableIdField.setAccessible(true);
        Object value = org.springframework.util.ReflectionUtils.getField(tableIdField, bean);
        Assert.notNull(value, "主键字段值为空");
        updateWrapper.eq(tableIdField.getAnnotation(TableId.class).value(), value);
    }

    public static <T> void setWrapperSet(T bean, UpdateWrapper<T> updateWrapper) {
        Class<?> aClass = bean.getClass();
        org.springframework.util.ReflectionUtils.doWithFields(aClass, field -> {
            field.setAccessible(true);
            Object value = org.springframework.util.ReflectionUtils.getField(field, bean);
            if (!ObjectUtils.isEmpty(value) && !Modifier.isStatic(field.getModifiers())) {
                TableField tableField = field.getAnnotation(TableField.class);
                if (tableField != null) {
                    updateWrapper.set(tableField.value(), value);
                }
            }
        });
    }
}
